package de.stefanocke.roo.kontaktliste.web.common;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.FormComponent;
import org.apache.wicket.markup.html.form.LabeledWebMarkupContainer;
import org.apache.wicket.markup.html.form.SimpleFormComponentLabel;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.ResourceModel;

import de.stefanocke.roo.wicket.RooStereotype;

/**
 * A fragment used to decorate a component, for example a form field. Propagates
 * its model to the decorated component. This allows the decorator to be used
 * with CPM as if they were the component itself.
 * <p>
 * Note that it's not a decorator in GOF sense, since it does not implement the
 * interface of the decorated component.
 * <p>
 * Fragments are used here, since decorations are supposed to have little
 * markup.
 * <p>
 * Cases, where the decorated component has little markup itself (f.e. <input>
 * or <select> tag for a form field) are also supported, by optionally creating
 * an inner fragment.
 * <p>
 * By default, a label is created as part of the decorator. If the component is
 * a {@link LabeledWebMarkupContainer}, a FormComponentLabel will be created and
 * the according markup must use the <label> tag.
 * 
 * @author stoc
 * 
 * @param <C> the type of the component
 */
@RooStereotype("wicket.DecoratorFragement")
public abstract class DecoratorFragment<C extends Component> extends Fragment implements ILabeled {

	/***/
	private static final long serialVersionUID = 1L;

	/***/
	protected static final String COMPONENT_ID = "comp";

	/**The id for the optional inner fragment.*/
	protected static final String INNER_FRAGMENT_ID = "innerFragment";
	
	/**The id for the outer conatiner, that contains the label and the component*/
	protected static final String CONTAINER_ID = "container";

	/***/
	protected static final String LABEL_ID = "label";

	public DecoratorFragment(String id, String markupId, MarkupContainer markupProvider) {
		super(id, markupId, markupProvider);
		init(markupProvider, null);

	}

	public DecoratorFragment(String id, String markupId, String innerFragmentMarkupId, MarkupContainer markupProvider) {
		super(id, markupId, markupProvider);
		init(markupProvider, innerFragmentMarkupId);

	}

	private void init(MarkupContainer markupProvider, String innerFragmentMarkupId) {
		C component = createComponent(COMPONENT_ID, markupProvider);
		//create the outer container
		add(new WebMarkupContainer(CONTAINER_ID).setOutputMarkupId(true));
		initDecoration(component);
		// create and add the inner fragment, if requested
		if (innerFragmentMarkupId != null) {
			Fragment innerFragment = new Fragment(INNER_FRAGMENT_ID, innerFragmentMarkupId, markupProvider);
			getInnerFragmentContainer().add(innerFragment);
		}	
		// create and add the label
		Component label = createLabel(LABEL_ID, component);
		getLabelContainer().add(label);
		// add the component
		getComponentContainer().add(component);
	}

	protected Component createLabel(String labelId, C component) {

		// TODO: LabeledWebMarkupContainer currently does not provide setLabel
		// method. Thus, FormComponentLabel support (<label for="...">) is only
		// provided for FormComponents.
		if (component instanceof FormComponent) {
			FormComponent<?> lwmc = (FormComponent<?>) component;
			lwmc.setLabel(new ResourceModel(getId()));
			return new SimpleFormComponentLabel(LABEL_ID, lwmc);
		} else {
			return new Label(LABEL_ID, new ResourceModel(getId()));
		}
	}

	@Override
	public void setLabel(IModel<String> labelModel) {
		C component = getComponent();
		if (component instanceof FormComponent) {
			FormComponent<?> lwmc = (FormComponent<?>) component;
			lwmc.setLabel(labelModel);
		}
		getLabelComponent().setDefaultModel(labelModel);
	}

	@SuppressWarnings("unchecked")
	@Override
	public IModel<String> getLabel() {
		return (IModel<String>) getLabelComponent().getDefaultModel();
	}

	/**
	 * Inits the decorator. Must not add the component to the hierarchy.
	 * 
	 * @param component
	 */
	protected void initDecoration(C component) {
	}

	/**
	 * Gets the container where to add the component. By default, this is the
	 * inner fragment, if existing, and otherwise it is this object.
	 * 
	 * @return the container for the component.
	 */
	protected MarkupContainer getComponentContainer() {
		MarkupContainer innerFragment = getInnerFragment();
		if (innerFragment != null) {
			return innerFragment;
		} else {
			return getOuterContainer();
		}
	}

	/**
	 * Gets the decorated component by searching for a component with ID
	 * {@link #COMPONENT_ID} in the container as determined by
	 * {@link #getComponentContainer()}.
	 * 
	 * @return the component
	 */
	@SuppressWarnings("unchecked")
	public C getComponent() {
		return (C) getComponentContainer().get(COMPONENT_ID);
	}

	/**
	 * 
	 * @return the inner fragment or null, if no inner fragment has been
	 *         created.
	 */
	private MarkupContainer getInnerFragment() {
		return (MarkupContainer) getInnerFragmentContainer().get(INNER_FRAGMENT_ID);
	}

	/**
	 * 
	 * @return the container for the inner fragment. By default, this object.
	 */
	protected MarkupContainer getInnerFragmentContainer() {
		return getOuterContainer();
	}

	public Component getLabelComponent() {
		return getLabelContainer().get(LABEL_ID);
	}

	/**
	 * 
	 * @return the container for the label. By default, this object.
	 */
	protected MarkupContainer getLabelContainer() {
		return getOuterContainer();
	}

	/**
	 * 
	 * @return the outer container
	 */
	public MarkupContainer getOuterContainer() {
		return (MarkupContainer) get(CONTAINER_ID);
	}

	/**
	 * Creates the component to be decorated.
	 * 
	 * @param id the id to be used for the component
	 * @param markupProvider the markupProvider
	 * @return the component.
	 */
	protected C createComponent(String id, MarkupContainer markupProvider) {
		return createComponent(id);
	}

	protected C createComponent(String id) {
		throw new UnsupportedOperationException("Please override one of the createComponent methods!");
	}

	@Override
	protected void onInitialize() {
		super.onInitialize();
		// This is the first time, the CompoundPropertyModel of the parent
		// is available.
		// So, onModelChanged is called, to set the proper model for the
		// form component.
		onModelChanged();
	}

	/**
	 * Propagate the model change to the decorated component.
	 */
	@Override
	protected void onModelChanged() {
		super.onModelChanged();
		getComponent().setDefaultModel(getDefaultModel());
	}
}
